use std::{collections::HashMap, usize};

use crate::{client::*, nativs::*, protocol};

pub type Int1 = usize;
pub type Int2 = usize;
pub type Int3 = usize;
pub type Int4 = usize;
pub type ServerStatus = Int2;
pub type Capabilities = Int4;

pub const TYPE_ID_ERROR: u8 = 0xFF;
pub const TYPE_ID_EOF: u8 = 0xFE;
// It has the same signature as EOF, but may be issued by server only during handshake phase
pub const TYPE_ID_AUTH_SWITCH: u8 = 0xFE;
pub const TYPE_ID_LOCAL_INFILE: u8 = 0xFB;
pub const TYPE_ID_OK: u8 = 0;
pub const TYPE_ID_AUTH_MORE_DATA: u8 = 0x01;
pub const TYPE_ID_AUTH_NEXT_FACTOR: u8 = 0x02;

#[derive(Default, Debug, Clone)]
pub struct NativePacketPayload {
    pos: usize,
    head: Vec<u8>,
    raw: Vec<u8>,

    payload_length: Int4,
    sequence_id: Int1,
}

impl NativePacketPayload {
    // 解析数据包
    // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html#sect_protocol_basic_packets_packet
    pub fn new(raw: &[u8]) -> Self {
        let (head, payload) = raw.split_at(4);
        let mut npp = NativePacketPayload::default();
        npp.raw = head.to_vec();
        npp.head = head.to_vec();
        npp.pos = 0;
        // int<3>
        npp.payload_length = npp.read_int(IntegerDataType::INT3);
        // int<1>
        npp.sequence_id = npp.read_int(IntegerDataType::INT1);
        npp.raw = payload.to_vec();
        npp.pos = 0;
        npp
    }

    pub fn reset(&mut self) {
        self.pos = 0;
    }

    fn get_next(&mut self) -> usize {
        let n = self.raw[self.pos] as usize;
        self.pos += 1;
        n
    }

    fn set_next(&mut self, n: u8) {
        self.raw[self.pos] = n;
        self.pos += 1;
    }

    fn adjust_payload_length(&mut self, ){
        if self.pos > self.payload_length {
            self.payload_length = self.pos;
        }
    }

    /**
     * Is it a ERROR packet.
     *
     * @return true if it is a ERROR packet
     */
    pub fn is_ERR_packet(&self) -> bool {
        (self.raw[0] & 0xff) == TYPE_ID_ERROR
    }

    /**
     * Is it a EOF packet.
     * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_eof_packet.html
     *
     * @return true if it is a EOF packet
     */
    pub fn is_EOF_packet(&self) -> bool {
        (self.raw[0] & 0xff == TYPE_ID_EOF) && self.payload_length <= 5
    }

    /**
     * Is it an OK packet.
     * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
     *
     * @return true if it is an OK packet
     */
    pub fn is_OK_packet(&self) -> bool {
        (self.raw[0] & 0xff) == TYPE_ID_OK
    }

    /**
     * Is it an OK packet for ResultSet. Unlike usual 0x00 signature it has 0xfe signature.
     * See https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_ok_packet.html
     *
     * @return true if it is an OK packet for ResultSet
     */
    pub fn is_resultset_OK_packet(&self) -> bool {
        (self.raw[0] & 0xff == TYPE_ID_EOF)
            && self.payload_length > 5
            && self.payload_length < 16777215
    }

    pub fn write_int(&mut self, typ: IntegerDataType, x: usize) {
        match typ {
            IntegerDataType::INT1 => {
                self.set_next((x & 0xff) as u8);
            }
            IntegerDataType::INT2 => {
                self.set_next((x & 0xff) as u8);
                self.set_next((x >> 8) as u8);
            }
            IntegerDataType::INT3 => {
                self.set_next((x & 0xff) as u8);
                self.set_next((x >> 8) as u8);
                self.set_next((x >> 16) as u8);
            }
            IntegerDataType::INT4 => {
                self.set_next((x & 0xff) as u8);
                self.set_next((x >> 8) as u8);
                self.set_next((x >> 16) as u8);
                self.set_next((x >> 24) as u8);
            }
            IntegerDataType::INT6 => {
                self.set_next((x & 0xff) as u8);
                self.set_next((x >> 8) as u8);
                self.set_next((x >> 16) as u8);
                self.set_next((x >> 24) as u8);
                self.set_next((x >> 32) as u8);
                self.set_next((x >> 40) as u8);
            }
            IntegerDataType::INT8 => {
                self.set_next((x & 0xff) as u8);
                self.set_next((x >> 8) as u8);
                self.set_next((x >> 16) as u8);
                self.set_next((x >> 24) as u8);
                self.set_next((x >> 32) as u8);
                self.set_next((x >> 40) as u8);
                self.set_next((x >> 48) as u8);
                self.set_next((x >> 56) as u8);
            }
            IntegerDataType::INT_LENENC => {
                if x < 251 {
                    self.write_int(IntegerDataType::INT1, x);
                } else if x < 65536 {
                    self.write_int(IntegerDataType::INT1, 252);
                    self.write_int(IntegerDataType::INT2, x);
                } else if x < 16777216 {
                    self.write_int(IntegerDataType::INT1, 253);
                    self.write_int(IntegerDataType::INT3, x);
                } else {
                    self.write_int(IntegerDataType::INT1, 254);
                    self.write_int(IntegerDataType::INT8, x);
                }
            }
        }

        self.adjust_payload_length();

    }

    
    pub fn read_int(&mut self, typ: IntegerDataType) -> usize {
        let mut b: usize = 0;
        match typ {
            IntegerDataType::INT1 => {
                b |= (self.get_next() & 0xff) as usize;
                b
            }
            IntegerDataType::INT2 => {
                b |= (self.get_next() & 0xff) as usize;
                b |= (self.get_next() << 8) as usize;
                b
            }
            IntegerDataType::INT3 => {
                b |= (self.get_next() & 0xff) as usize;
                b |= (self.get_next() << 8) as usize;
                b |= (self.get_next() << 16) as usize;
                b
            }
            IntegerDataType::INT4 => {
                b |= (self.get_next() & 0xff) as usize;
                b |= (self.get_next() << 8) as usize;
                b |= (self.get_next() << 16) as usize;
                b |= (self.get_next() << 24) as usize;
                b
            }
            IntegerDataType::INT6 => {
                b |= (self.get_next() & 0xff) as usize;
                b |= (self.get_next() << 8) as usize;
                b |= (self.get_next() << 16) as usize;
                b |= (self.get_next() << 24) as usize;
                b |= (self.get_next() << 32) as usize;
                b |= (self.get_next() << 40) as usize;
                b
            }
            IntegerDataType::INT8 => {
                b |= (self.get_next() & 0xff) as usize;
                b |= (self.get_next() << 8) as usize;
                b |= (self.get_next() << 16) as usize;
                b |= (self.get_next() << 24) as usize;
                b |= (self.get_next() << 32) as usize;
                b |= (self.get_next() << 40) as usize;
                b |= (self.get_next() << 48) as usize;
                b |= (self.get_next() << 56) as usize;
                b
            }
            IntegerDataType::INT_LENENC => {
                let sw = (self.raw[self.pos] & 0xff).into();
                self.pos += 1;
                match sw {
                    251 => 0,
                    252 => self.read_int(IntegerDataType::INT2),
                    253 => self.read_int(IntegerDataType::INT3),
                    254 => self.read_int(IntegerDataType::INT8),
                    _ => sw,
                }
            }
        }

    }

    fn skip_bytes(&mut self, typ: StringSelfDataType) {
        match typ {
            StringSelfDataType::STRING_TERM => {
                while self.pos < self.raw.len() && self.raw.get(self.pos).unwrap_or(&0) != &0 {
                    self.pos += 1;
                }
                self.pos += 1;
            }
            StringSelfDataType::STRING_EOF => self.pos = self.payload_length,
            StringSelfDataType::STRING_LENENC => {
                self.pos += self.read_int(IntegerDataType::INT_LENENC)
            }
        }
    }

    pub fn write_bytes(&mut self, typ: StringSelfDataType, data: &[u8]) {

        println!("data:len:{}", data.len());
        match typ {
            StringSelfDataType::STRING_EOF => {
                self.write_more_bytes(StringLengthDataType::STRING_FIXED, data, 0, data.len());
            },

            StringSelfDataType::STRING_TERM => {
                self.write_more_bytes(StringLengthDataType::STRING_FIXED, data, 0, data.len());
                self.raw[self.pos] = 0;
                self.pos += 1;
            },
            StringSelfDataType::STRING_LENENC => {
                self.write_int(IntegerDataType::INT_LENENC, data.len());
                self.write_more_bytes(StringLengthDataType::STRING_FIXED, data, 0, data.len());
            }
        }
    }

    pub fn read_bytes(&mut self, typ: StringSelfDataType) -> Vec<u8> {
        // FixedLengthString
        // NullTerminatedString
        // VariableLengthString
        // LengthEncodedString
        // RestOfPacketString

        match typ {
            StringSelfDataType::STRING_TERM => {
                // let mut data = Vec::new();
                // for _ in self.pos..self.raw.len() {
                //     let c = self.raw.remove(self.pos);
                //     self.pos += 1;
                //     if c == 0 {
                //         break;
                //     }
                //     data.push(c);
                // }
                // data
                let mut i = self.pos;
                while i < self.raw.len() && self.raw.get(i).unwrap_or(&0) != &0 {
                    i += 1;
                }
                let data = self.read_more_bytes(StringLengthDataType::STRING_FIXED, i - self.pos);
                self.pos += 1;
                data
            }
            StringSelfDataType::STRING_EOF => self.read_more_bytes(
                StringLengthDataType::STRING_FIXED,
                self.payload_length as usize - self.pos as usize,
            ),

            StringSelfDataType::STRING_LENENC => {
                let len = self.read_int(IntegerDataType::INT_LENENC);
                match self.read_int(IntegerDataType::INT_LENENC) {
                    0 => Vec::new(),
                    _ => self.read_more_bytes(StringLengthDataType::STRING_FIXED, len as usize),
                }
            }
        }

        // String::from_utf8_lossy(&data).to_string()
    }

    fn read_str(&mut self, typ: StringSelfDataType) -> String {
        String::from_utf8_lossy(&self.read_bytes(typ)).to_string()
    }

    pub fn write_more_bytes(&mut self, typ: StringLengthDataType, data: &[u8], offset: usize, len: usize) {
        match typ {
            StringLengthDataType::STRING_FIXED | StringLengthDataType::STRING_VAR => {
                for i in offset..len {
                    self.raw.push(data[i]);
                    self.pos += 1;
                }
            }
        }
    }

    pub fn read_more_bytes(&mut self, typ: StringLengthDataType, len: usize) -> Vec<u8> {

        match typ {
            StringLengthDataType::STRING_FIXED | StringLengthDataType::STRING_VAR => {
                let mut data = Vec::new();
                //let len = self.pos + len;
                println!("POS: {}, LEN: {}", self.pos, len);
                for _ in 0..len {
                    data.push(self.get_next() as u8);
                }
                data
            }
        }
    }

    pub fn read_fix_str(&mut self, len: usize) -> String {
        let b = self.read_more_bytes(StringLengthDataType::STRING_FIXED, len);
        String::from_utf8_lossy(&b).to_string()
    }

    pub fn read_header(&mut self) -> usize {
        self.read_int(IntegerDataType::INT1)
    }

    // // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_err_packet.html
    // fn parse_err(&mut self, data: &[u8]) {
    //     self.reset();
    //     self.raw = data.to_vec();

    //     // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_basic_packets.html#sect_protocol_basic_packets_packet
    //     // int<3>
    //     self_length = self.read_int(IntegerDataType::INT3);
    //     // int<1>
    //     self.sequence_id = self.read_int(IntegerDataType::INT1);

    // }

    pub fn get_raw(&mut self) -> &[u8] {
        let len = self.raw.len();
        let pos = self.pos;
        let mut swap = self.raw.clone();
        self.raw = [0; 4].to_vec();
        self.pos = 0;

        self.write_int(IntegerDataType::INT3, len);
        self.write_int(IntegerDataType::INT1, 1);

        for (i, n) in self.raw.iter().enumerate() {
            swap.insert(i, *n);
        }
        self.raw = swap;
        self.pos = pos;
        &self.raw
    }

}

// https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase.html
// Payload for V10
#[derive(Default, Debug, Clone)]
pub struct NativeCapabilities {
    inner: NativePacketPayload,

    // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html
    // int<1>
    protocol_version: Int1,
    // string<NUL>
    server_version: String,
    // int<4>
    thread_id: Int4,
    // string[8]
    auth_plugin_data_part1: Vec<u8>,
    // int<1>
    filler: Int1, // 0x00
    // int<2>
    // capability_flags_1: Int2,
    // int<1>
    character_set: Int1,
    // int<2> https://dev.mysql.com/doc/dev/mysql-server/latest/mysql__com_8h.html#a1d854e841086925be1883e4d7b4e8cad
    status_flags: Int2,
    // int<2>
    // capability_flags_2: Int2,
    capability_flags: Int4,
    // https://dev.mysql.com/doc/dev/mysql-server/latest/group__group__cs__capabilities__flags.html
    // int<1>
    auth_plugin_data_len: Int1,

    // string[10]
    reserved: Vec<u8>,
    auth_plugin_data_part2: Vec<u8>,

    auth_plugin_name: String,
}

impl NativeCapabilities {
    pub fn new(initial_handshake_packet: &NativePacketPayload) -> Self {
        let mut native_capabilities = NativeCapabilities::default();
        native_capabilities.inner = initial_handshake_packet.clone();
        native_capabilities
    }

    pub fn get_initial_handshake_packet(&self) -> NativePacketPayload {
        self.inner.clone()
    }

    // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_connection_phase_packets_protocol_handshake_v10.html
    // https://blog.csdn.net/prestigeding/article/details/69547289
    // main/protocol-impl/java/com/mysql/cj/protocol/a/NativeAuthenticationProvider.java
    pub fn handle(&mut self) {
        // Payload
        self.protocol_version = self.inner.read_int(IntegerDataType::INT1);
        self.server_version = self.inner.read_str(StringSelfDataType::STRING_TERM);
        self.thread_id = self.inner.read_int(IntegerDataType::INT4);

        // read auth-plugin-data-part-1 (string[8])
        self.auth_plugin_data_part1 = self
            .inner
            .read_more_bytes(StringLengthDataType::STRING_FIXED, 8);

        // read filler ([00])
        self.inner.read_int(IntegerDataType::INT1);

        let mut capability_flags = 0;
        // read capability flags (lower 2 bytes)
        if self.inner.pos < self.inner.payload_length {
            capability_flags = self.inner.read_int(IntegerDataType::INT2);
        }

        self.character_set = self.inner.read_int(IntegerDataType::INT1);
        self.status_flags = self.inner.read_int(IntegerDataType::INT2);

        capability_flags |= self.inner.read_int(IntegerDataType::INT2) << 16;
        self.capability_flags = capability_flags;

        // 65535, 57343
        // 0xDFFFFFFF
        if (capability_flags & CLIENT_PLUGIN_AUTH) != 0 {
            self.auth_plugin_data_len = self.inner.read_int(IntegerDataType::INT1)
        } else {
            // read filler ([00])
            self.inner.read_int(IntegerDataType::INT1);
        }

        //
        // self.reserved = self
        //     .inner
        //     .read_more_bytes(StringLengthDataType::STRING_FIXED, 10);
        self.inner.pos += 10;

        let client_ssl_mode = SslMode::DISABLED;

        // check SSL availability
        if (capability_flags & CLIENT_SSL) == 0
            && client_ssl_mode != SslMode::DISABLED
            && client_ssl_mode != SslMode::PREFERRED
        {
            panic!(
                "UnableToConnectException: Check SSL availability, client_ssl_mode={:?}",
                client_ssl_mode
            );
        } else if (capability_flags & CLIENT_RESERVED2) == 0 {
            panic!(
                "UnableToConnectException: CLIENT_SECURE_CONNECTION(CLIENT_RESERVED2) is required"
            )
        } else if (capability_flags & CLIENT_PLUGIN_AUTH) == 0 {
            panic!("UnableToConnectException: CLIENT_PLUGIN_AUTH is required")
        }

        // auth_plugin_data_part2_len = auth_plugin_data_len - length(auth-plugin-data-part-1)
        // let auth_plugin_data_part2_len = std::cmp::max(13, self.auth_plugin_data_len - 8);
        // read string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
        self.auth_plugin_data_part2 = if self.auth_plugin_data_len > 0 {
            self.inner.read_more_bytes(
                StringLengthDataType::STRING_FIXED,
                self.auth_plugin_data_len - 8,
            )
        } else {
            self.inner.read_bytes(StringSelfDataType::STRING_TERM)
        };

        self.auth_plugin_name = String::from_utf8_lossy(&self.inner.read_more_bytes(
            StringLengthDataType::STRING_FIXED,
            self.auth_plugin_data_len,
        ))
        .to_string();

        println!("{:?}", self);
    }

    pub fn get_capability_flags(&mut self) -> usize {
        self.capability_flags
    }
}

pub struct HandshakeResponse41 {
    // inner: NativePacketPayload,

    // 	Capabilities Flags, CLIENT_PROTOCOL_41 always set.
    client_flag: Int4,

    // maximum packet size
    max_packet_size: Int4,

    // client charset a_protocol_character_set, only the lower 8-bits
    character_set: Int1,

    // string[23]
    filler: Vec<u8>,

    // string<NUL>
    // login user name
    username: String,

    // length of auth_response
    auth_response_length: Option<Int1>,
    // opaque authentication response data generated by Authentication Method indicated by the plugin name field.
    auth_response: String,

    // initial database for the connection. This string should be interpreted using the character set indicated by character set field.
    database: Option<String>,

    // the Authentication Method used by the client to generate auth-response value in this packet. This is an UTF-8 string.
    client_plugin_name: Option<String>,

    // if capabilities & CLIENT_CONNECT_ATTRS {
    //     int<lenenc>	length of all key-values	affected rows
    //     string<lenenc>	key1	Name of the 1st client attribute
    //     string<lenenc>	value1	Value of the 1st client attribute
    //     .. (if more data in length of all key-values, more keys and values parts)
    //     }
    zstd_compression_level: Option<Int1>,
}

impl HandshakeResponse41 {
    pub fn new(username: String) -> Self {
        HandshakeResponse41 {
            client_flag: 0,
            max_packet_size: 16384,
            character_set: 255,
            filler: [
                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            ]
            .to_vec(),
            username,
            auth_response_length: None,
            auth_response: String::new(),
            database: None,
            client_plugin_name: None,
            zstd_compression_level: None,
        }
    }

    // pub fn write(&mut self) -> Vec<u8> {

    //     NativePacketPayload::new(raw)

    // }
}
